/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*************************************************************************
 * File: client.cpp
 *************************************************************************
 *\author: starlove
 *************************************************************************/

#include <network/peer.h>
#include <platform/string.h>
#include <stdio.h>

NETWORKBUFFER CPeer::m_buffer;

CPeer::CPeer()
    :m_isio(true)
    ,m_pSocket(NULL)
    ,m_raw_sck(INVALID_SOCKET)
    ,m_pMachine(NULL)
    ,m_data_tp(ePEERDTP_PACKET)
{
}

CPeer::CPeer(ISocket *pSocket, SOCKET sck /* = INVALID_SOCKET */)
{
    Init(pSocket, sck);
}

CPeer::~CPeer()
{
}

void CPeer::Init(ISocket *pSocket, SOCKET sck /* = INVALID_SOCKET */)
{
    m_pSocket = pSocket;
    m_raw_sck = sck;
}

bool CPeer::Connect(const char *szAddr, unsigned int uPort)
{
    if (!m_pSocket)
        return false;

    m_pSocket->BindProcessor((IEventProcessor *)this);
    m_raw_sck = m_pSocket->Connect(szAddr, uPort);
    if (m_raw_sck == INVALID_SOCKET)
    {
        return false;
    }
    m_isio = false;
    return true;
}

void CPeer::Destory()
{
    if(m_raw_sck == INVALID_SOCKET)
        return;
    if(m_pMachine)
        m_pMachine->OnClose(m_raw_sck);
    UnRegisterPeer(this);
    m_pSocket->Close(m_raw_sck);
    m_raw_sck = INVALID_SOCKET;
}

int CPeer::SendData(char *szData, unsigned int size)
{
    if(m_isio)
    {
        if(szData)
            GetSendBuffer()->Append(szData, size);
        if ( -1 == m_pSocket->IOSend(m_raw_sck, this, (IEventProcessor *)this) )
        {
            DEBUG_INFO_NETWORK(m_raw_sck, "IOSend fail");
            OnError();
            return -1;
        }
        //DEBUG_INFO_NETWORK(m_raw_sck, "IOSend %d", size);
        return size;
    }
    else
    {
        char *szd = szData;
        unsigned int ss = size;
        if(!szd)
        {
            szd = GetSendBuffer()->GetPtr();
            ss = GetSendBuffer()->GetDataSize();
        }
        if(ss == 0)
            return 0;
        SOCKET s = m_raw_sck;
        int r = m_pSocket->Send(s, szd, ss);
        if ( s == INVALID_SOCKET )
        {
            OnError();
            DEBUG_INFO_NETWORK(m_raw_sck, "send fail, close");
            return -1;
        }
        return r;
    }
    return -1;
}

int CPeer::RecvFromFd()
{
    if(!m_isio)
    {
        char szTmpData[__MLSE_NOIO_RECV_BUFFSIZE__];
        LPNETWORKBUFFER buf = GetRecvBuffer();
        int re = 0;
        int wait_size = __MLSE_NOIO_RECV_BUFFSIZE__;
        if (buf->GetDataSize() > sizeof(PACKET_FIXED_HEADER))
        {
            wait_size = (buf->GetSize() - buf->GetDataSize());
        }
        SOCKET s = m_raw_sck;
        if ( (re = m_pSocket->Recv(s, szTmpData, std::min<int>(wait_size, __MLSE_NOIO_RECV_BUFFSIZE__))) > 0 )
        {
            buf->Append(szTmpData, re);
        }
        if ( s == INVALID_SOCKET )
        {
            DEBUG_INFO_NETWORK(m_raw_sck, "recv fail, close");
            OnError();
            return -1;
        }
        return re;
    }
    return 0;
}

int CPeer::PostNextRecv()
{
    if(m_isio)
    {
        if ( -1 == m_pSocket->IORecv(m_raw_sck, this, (IEventProcessor *)this) )
        {
            DEBUG_INFO_NETWORK(m_raw_sck, "IORecv fail");
            OnError();
            return -1;
        }
    }
    return 0;
}

int CPeer::RecvData()
{
    RecvFromFd();
    LPPACKET pPacket = NULL;
    if (GetRecvBuffer()->GetDataSize() < sizeof(PACKET_FIXED_HEADER))
        goto __post_next_recv;
__process_read_ready:
    pPacket = (LPPACKET)GetRecvBuffer()->GetPtr();
    if(pPacket->header.length > GetRecvBuffer()->GetDataSize())
    {
        GetRecvBuffer()->Expand(pPacket->header.length);
    }
    else
    {
        m_buffer.Reset();
        pPacket->UnPack(&m_buffer);
        LPPACKET pFainlPacket = (LPPACKET)m_buffer.GetPtr();

        OnReadReady(pFainlPacket);

        if(m_pMachine && MASK_TEST(pFainlPacket->header.base.flag, ePF_ENCODE))
            m_pMachine->OnPacket(m_raw_sck, pFainlPacket);

        if ( GetRecvBuffer()->Align(pPacket->header.length) )
        {
            if(GetRecvBuffer()->GetDataSize() >= sizeof(PACKET_FIXED_HEADER))
            {
                goto __process_read_ready;
            }
        }
        else
        {
            if (GetRecvBuffer()->IsDamaged())
            {
                DEBUG_INFO_NETWORK(m_raw_sck, "Buffer is damaged");
                OnError();
                return -1;
            }
        }
    }


__post_next_recv:
    return PostNextRecv();
}

int CPeer::RecvHttpData()
{
    RecvFromFd();
    unsigned int cpsize = GetRecvBuffer()->GetDataSize();
    if (cpsize < __MLSE_NETWORK_HTTP_RQUEST_MINSIZE__)
        return PostNextRecv();

    const char *ptrData = GetRecvBuffer()->GetPtr();
    const char *pHeaderEnd = strstr(ptrData, __MLSE_NETWORK_HTTP_HEADER_DELIMITERS__);
    if(!pHeaderEnd)
        return PostNextRecv();

    if(!CHTTPParser::parse_request_action(ptrData, &m_ha))
    {
        OnError();
        return -1;
    }

    const char *pContent = NULL;
    if(m_ha.tp == __MLSE_NETWORK_HTTP_POST__)
    {
        unsigned int length = CHTTPParser::parse_length(ptrData, cpsize);
        if(length == 0 || length > cpsize)
        {
            OnError();
            return -1;
        }

        if(cpsize < (unsigned int)length)
        {
            OnError();
            return -1;
        }
        if(pHeaderEnd)
            pContent = pHeaderEnd + strlen(__MLSE_NETWORK_HTTP_HEADER_DELIMITERS__);

        if(!pContent || pContent - ptrData >= cpsize)
            return PostNextRecv();

        unsigned int nbodysize = length;
        if(nbodysize > 0)
        {
            m_form_data.Expand(nbodysize + 1);
            m_form_data.Append(pContent, nbodysize);
            char e = 0;
            m_form_data.Append(&e, 1);
        }
    }

    const char *pHeaderLine = strstr(ptrData, "\r\n");
    CMapWrapper requestHeader;

    do
    {
        pHeaderLine += strlen(__MLSE_NETWORK_HTTP_LINE_DELIMITERS__);
        static char szKTmp[32];
        static char szVTmp[255];
        const char *second = strpbrk(pHeaderLine,__MLSE_NETWORK_HTTP_KEY_DELIMITERS__);
        if(!second)
        {
            OnError();
            return -1;
        }
        const char *first = pHeaderLine;
        pHeaderLine = strstr(pHeaderLine, __MLSE_NETWORK_HTTP_LINE_DELIMITERS__);

        int cpsize = std::min<unsigned int>(31, second - first);
        memcpy(szKTmp, first, cpsize);
        szKTmp[cpsize] = 0;
        second += strlen(__MLSE_NETWORK_HTTP_KEY_DELIMITERS__);
        if(second >= pHeaderLine)
        {
            cpsize = 0;
        }
        else
        {
            cpsize = std::min<unsigned int>(254, pHeaderLine - second);
            memcpy(szVTmp, second, cpsize);
        }
        szVTmp[cpsize] = 0;

        first = trim(szKTmp, 31);
        second = trim(szVTmp, 254);
        if(strlen(first) > 0)
            requestHeader.Set<ENCODESTR, ENCODESTR>(first, second);
    }
    while(pHeaderLine && pHeaderEnd > pHeaderLine);

    requestHeader.Set<ENCODESTR, ENCODESTR>("HTTP_METHOD", CHTTPParser::get_method_name(m_ha.tp));
    requestHeader.Set<ENCODESTR, ENCODESTR>("HTTP_URL", m_ha.url);
    requestHeader.Set<ENCODESTR, ENCODESTR>("HTTP_QUERYSTRING", m_ha.qs);
    requestHeader.Set<ENCODESTR, int>("HTTP_VER", m_ha.ver);
    requestHeader.Set<ENCODESTR, ENCODESTR>("HTTP_FORM_DATA", m_form_data.GetPtr());


    if(!m_pMachine->OnHttp(m_raw_sck, requestHeader.GetBuffer()))
    {
        HTTPRESPONSE response;
        NETWORKBUFFER outBuff;
        ProcessHttpRequest(&requestHeader, &response, &outBuff);
        CHTTPParser::build_response(GetSendBuffer(), &response, &outBuff);
        SendData(NULL, 0);
    }
    return 0;
}

unsigned int CPeer::OnWriteReady(unsigned int nSent)
{
    LPNETWORKBUFFER buf = GetSendBuffer();
    if (!buf || buf->GetDataSize() == 0)
    {
    }
    else
    {
        unsigned int size = buf->GetDataSize();
        //DEBUG_INFO_NETWORK(m_raw_sck, "Pre Send length: %d",size);
        int ret = m_pSocket->Send(m_raw_sck, buf->GetPtr(), size);
        if (0 < ret)
        {
            //DEBUG_INFO_NETWORK(m_raw_sck, "Send length: %d",ret);
            buf->Align(ret);
            nSent = ret;
        }
    }

	if( nSent > 0 && GetSendBuffer()->GetDataSize() == 0 )
	{
		if(m_data_tp == ePEERDTP_HTTP)
			OnError();
	}

    return nSent;
}

void CPeer::OnEvent(LPEVENT ev, int event_sets)
{
    if (MASK_TEST(event_sets, ETP_ERROR))
    {
        OnError();
        return;
    }
    if (MASK_TEST(event_sets, ETP_IOERROR))
    {
        OnError();
        return;
    }
    if (MASK_TEST(event_sets, ETP_READ_READY))
    {
        if(ev->datasize == 0)
        {
            //DEBUG_INFO_NETWORK(m_raw_sck, "RECV 0");
            OnError();
            return;
        }
        else
        {
#ifdef __MLSE_PLATFORM_WIN__
            /// if using iocp, set the buffer-offset, because its io in the kernel-thread.
            GetRecvBuffer()->SetOffset(ev->datasize);
#endif
            switch(m_data_tp)
            {
            case ePEERDTP_PACKET:
            {
                RecvData();
                break;
            }
            case ePEERDTP_HTTP:
            {
            	RecvHttpData();
                break;
            }
            }
        }
    }
    if (MASK_TEST(event_sets, ETP_WRITE_READY))
    {
        OnWriteReady(ev->datasize);
        if ( GetSendBuffer()->GetDataSize() == 0 )
            return;
        if ( INVALID_SOCKET == m_pSocket->IOSend(m_raw_sck, this, (IEventProcessor *)this) )
        {
            OnError();
            return;
        }
    }

    m_pSocket->Close(ev);
}
